﻿using Sunny.FrameDecoder;
using System;
using System.Net;
using System.Threading;
using TouchSocket.Core;
using TouchSocket.Sockets;

namespace Sunny.Tcp
{
    public class TcpBaseClient : TouchSocket.Sockets.TcpClient
    {
        /// <summary>
        /// 标签
        /// </summary>
        public object Tag { get; set; }
    }

    /// <summary>
    /// TCP客户端
    /// </summary>
    public class TcpClient : IDisposable
    {
        private readonly TcpBaseClient _client;
        private BaseByteFrameDecoder _decoder;

        private bool _connected = false;
        private DateTime _lastConnect = DateTime.MinValue;
        private int _reconnectCount = 0;
        private Thread _thread;

        public TcpBaseClient Socket => _client;

        /// <summary>
        /// 是否连接成功
        /// </summary>
        public bool IsConnected => Ping.Exists() ? _connected && PongConnected : _connected;

        private bool PongConnected { get; set; }

        /// <summary>
        /// 解码器，主要做组包发送用途
        /// </summary>
        public BaseByteFrameDecoder FrameDecoder
        {
            get => _decoder;
            set
            {
                if (_decoder != null)
                {
                    _decoder.OnDecoder -= Decoder_OnDecoder;
                    _decoder.Dispose();
                    _decoder = null;
                }

                _decoder = value;
                _decoder.OnDecoder += Decoder_OnDecoder;
            }
        }

        /// <summary>
        /// 心跳包发送间隔
        /// </summary>
        public TimeSpan HeartInterval { get; set; } = TimeSpan.FromSeconds(20);

        /// <summary>
        /// 发送至此IP地址
        /// </summary>
        public string HostName { get; }

        /// <summary>
        /// 断线重连及心跳包线程是否开启
        /// </summary>
        private bool _threadStart;

        private string name;

        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get => name ?? this.GetType().Name;
            set => name = value;
        }

        /// <summary>
        /// 心跳包
        /// </summary>
        public byte[] Ping { get; set; }

        /// <summary>
        /// 服务端回复的心跳包
        /// </summary>
        public byte[] Pong { get; set; }

        /// <summary>
        /// 发送至此端口
        /// </summary>
        public int Port { get; }

        /// <summary>
        /// 断线重连时间间隔
        /// </summary>
        public TimeSpan ReconnectionTimeSpan { get; set; } = TimeSpan.FromSeconds(10);

        /// <summary>
        /// 断线重连次数，为0则无限次重连，否则按次数判断，到次数后断开
        /// </summary>
        public uint ReconnectionCount { get; set; } = 0;

        /// <summary>
        /// 标签
        /// </summary>
        public object Tag { get; set; }

        public event OnTcpClientConnectStateChanged OnConnected;
        public event OnTcpClientConnectStateChanged OnDisconnected;

        /// <summary>
        /// 错误回调事件
        /// </summary>
        public event OnTcpSocketError OnError;

        /// <summary>
        /// 接收数据事件
        /// </summary>
        public event OnTcpClientByteEventHandler OnReceived;

        protected virtual void DoConnected()
        {
            Console.WriteLine(Name + ": Connected.");
        }

        protected virtual void DoDisconnected()
        {
            Console.WriteLine(Name + ": Disconnected.");
        }

        protected virtual void DoError(Exception e)
        {
            Console.WriteLine(Name + ": Error: " + e.Message);
        }

        protected virtual void DoReceived(TcpClientByteDataEventArgs e) { }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="hostname">IP地址</param>
        /// <param name="port">端口</param>
        public TcpClient(string hostname, int port)
        {
            if (!hostname.IsIP4())
            {
                throw new ArgumentException(null, nameof(hostname));
            }

            if (!port.ValidateTcpPort())
            {
                throw new ArgumentException(null, nameof(port));
            }

            HostName = hostname;
            Port = port;

            _client = new();
            _client.Connected = (client, e) =>
            {
                ConnectedTime = DateTime.Now;
                _connected = true;

                if (Ping.Exists())
                {
                    Send(Ping);
                }
                else
                {
                    DoConnected();
                    OnConnected?.Invoke(this, new TcpClientEventArgs(this));
                }

                return EasyTask.CompletedTask;
            };

            _client.Disconnected = (client, e) =>
            {
                if (_connected) _connected = false;
                DoDisconnected();
                OnDisconnected?.Invoke(this, new TcpClientEventArgs(this));

                return EasyTask.CompletedTask;
            };

            _client.Received = (client, e) =>
            {
                LastReceiveDataTime = DateTime.Now;
                byte[] data = e.ByteBlock.ToArray();

                if (Pong.Exists() && data.AsSpan().IndexOf(Pong) >= 0 && !PongConnected)
                {
                    PongConnected = true;
                    DoConnected();
                    OnConnected?.Invoke(this, new TcpClientEventArgs(this));
                }

                if (FrameDecoder != null)
                {
                    FrameDecoder.Decode(data);
                }
                else
                {
                    var args = new TcpClientByteDataEventArgs(this, data);
                    DoReceived(args);
                    OnReceived?.Invoke(this, args);
                }

                return EasyTask.CompletedTask;
            };

            TouchSocketConfig config = new TouchSocketConfig().SetRemoteIPHost(new IPHost(IPAddress.Parse(HostName), port));
            _client.Setup(config);
        }

        /// <summary>
        /// 析构函数
        /// </summary>
        public virtual void Dispose()
        {
            Disconnect();
            _client?.Dispose();
        }

        private int ConnectTimeOut = 5000;
        /// <summary>
        /// 开启接收数据线程
        /// </summary>
        public void Connect(int connectTimeOut = 5000)
        {
            ConnectTimeOut = connectTimeOut;
            if (_threadStart) return;
            _threadStart = true;
            _reconnectCount = 0;
            _lastConnect = DateTime.MinValue;
            _connected = false;
            PongConnected = false;
            ConnectedTime = LastReceiveDataTime = LastSendDataTime = DateTime.MinValue;

            _thread = new Thread(OnThread) { IsBackground = true };
            _thread.Start();//开启
        }

        /// <summary>
        /// 线程休眠时间间隔
        /// </summary>
        public TimeSpan ThreadSleepTimeSpan { get; set; } = TimeSpan.FromMilliseconds(500);

        private void OnThread()
        {
            while (_threadStart)
            {
                if (!_connected && DateTime.Now - _lastConnect > ReconnectionTimeSpan)
                {
                    if (_reconnectCount > 0)
                        Console.WriteLine(Name + ": Try reconnect to " + HostName + ":" + Port + ", Count " + _reconnectCount);
                    else
                        Console.WriteLine(Name + ": Try connect to " + HostName + ":" + Port);

                    _lastConnect = DateTime.Now;
                    var result = _client.TryConnect(ConnectTimeOut);
                    _connected = result.ResultCode == TouchSocket.Core.ResultCode.Success;

                    if (!_connected)
                    {
                        DoDisconnected();
                        OnDisconnected?.Invoke(this, new TcpClientEventArgs(this));
                    }

                    if (result.ResultCode == ResultCode.Exception)
                    {
                        DoError(new Exception("Connected, " + result.Message));
                        OnError?.Invoke(this, new Exception("Connected, " + result.Message));
                    }

                    if (_connected)
                    {
                        _reconnectCount = 0;
                    }
                    else
                    {
                        if (ReconnectionTimeSpan > TimeSpan.Zero)
                        {
                            _reconnectCount++;
                            if (ReconnectionCount > 0 && _reconnectCount > ReconnectionCount)
                            {
                                Console.WriteLine(Name + ": Reconnection times over " + ReconnectionCount + " times.");
                                break;
                            }
                        }
                        else
                        {
                            Console.WriteLine(Name + ": No need reconnection.");
                            break;
                        }
                    }
                }

                if (DateTime.Now - LastSendDataTime > HeartInterval && _connected)
                {
                    if (Ping.Exists()) Send(Ping);
                }

                Thread.Sleep(ThreadSleepTimeSpan);
            }

            Disconnect();
        }

        /// <summary>
        /// 停止接收数据线程
        /// </summary>
        public void Disconnect()
        {
            if (!_threadStart) return;
            _threadStart = false;
            Close();
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="data">数据</param>
        public void Send(byte[] buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("Data cannot be empty.");
            }

            TrySend(buffer);
        }

        private void TrySend(byte[] buffer)
        {
            try
            {
                if (_connected && _client.CanSend)
                {
                    _client.Send(buffer);
                    LastSendDataTime = DateTime.Now;
                }
            }
            catch (Exception ex)
            {
                _connected = false;
                DoError(ex);
                OnError?.Invoke(this, ex);
            }
        }

        public DateTime ConnectedTime { get; private set; }

        public DateTime LastReceiveDataTime { get; private set; }

        public DateTime LastSendDataTime { get; private set; }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="offset">起始位置</param>
        /// <param name="size">长度</param>
        public void Send(byte[] buffer, int offset, int size)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("Data cannot be empty.");
            }

            if (offset < 0 || offset > buffer.Length)
            {
                throw new ArgumentOutOfRangeException("Index must be greater than or equal to zero.");
            }

            if (size < 0 || size > buffer.Length - offset)
            {
                throw new ArgumentOutOfRangeException("Length must be less than or equal data length.");
            }

            Send(buffer.AsSpan().Slice(offset, size).ToArray());
        }

        private void Close()
        {
            if (_connected)
            {
                try
                {
                    _client.MainSocket.Shutdown(System.Net.Sockets.SocketShutdown.Both);
                }
                finally
                {
                    _client.Close();
                }
            }

            _connected = false;
        }

        private void Decoder_OnDecoder(object sender, IByteEventArgs e)
        {
            var args = new TcpClientByteDataEventArgs(this, e);
            DoReceived(args);
            OnReceived?.Invoke(this, args);
        }
    }
}